package com.hoolai.synroom.dispatcher;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;

import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.hoolai.synroom.config.CmdsConfig;
import com.hoolai.synroom.config.ReqCmdProperties;
import com.hoolai.synroom.logic.ExceptionHandler;
import com.hoolai.synroom.logic.SynRoom;
import com.hoolai.synroom.logic.SynRoomPlayer;
import com.hoolai.synroom.threadpool.GlobalBusinessThreadPool;
import com.hoolai.synroom.threadpool.SynRoomBusinessThreadPool;
import com.hoolai.synroom.threadpool.SynRoomTask;

/**
 * <b>功能描述：</b>负责命令的调度，注意，这里有一个这样的约定，request的proto对应service的方法参数（除最后一个），
 * service中还会多一个参数Context<br>
 * <b>项目名称：</b>gamelogic3<br>
 * <b>修改记录：</b><br>
 * 
 * @author yizhuan
 * @date 2011-12-22上午09:26:38<br>
 *       TODO
 */
public class CmdDispatcher {
    /**
     * key = serviceName + "." + method + "." + First para's protoJavaType + "."
     * + Second para's protoJavaType...
     */
    private MethodInvoker[] methodInvokers;

    private ApplicationContext ac;

    private static final Logger logger = Logger.getLogger("synfight");

    private final CmdsConfig cmdsConfig;

    private SynRoomBusinessThreadPool businessThreadPool;

    private GlobalBusinessThreadPool globalThreadPool;

    private ExceptionHandler exceptionHandler;

    public CmdDispatcher(CmdsConfig cmdsConfig, SynRoomBusinessThreadPool businessThreadPool,
            GlobalBusinessThreadPool globalThreadPool, ExceptionHandler exceptionHandler) {
        this.cmdsConfig = cmdsConfig;
        this.businessThreadPool = businessThreadPool;
        this.globalThreadPool = globalThreadPool;
        this.exceptionHandler = exceptionHandler;
    }

    /**
     * 只允许在服务器开始的时候调用
     * 
     * @param ac
     */
    public void init(ApplicationContext ac) {
        this.ac = ac;
        initServiceMethodMap();
    }


    private void initServiceMethodMap() {
        methodInvokers=new MethodInvoker[cmdsConfig.getReqCmds().length];
        for (int i=0 ; i < cmdsConfig.getReqCmds().length; i++) {
            ReqCmdProperties cmdProperties = cmdsConfig.getReqCmds()[i];
            if(null == cmdProperties){
                continue;
            }
            String serviceName = cmdProperties.getServiceName();
            String methodName = cmdProperties.getMethodName();
            Object service = ac.getBean(serviceName);
            if (service == null) {
                logger.error("init cmd.xml'serviceMethod definition, but can't find the service: serviceName = "
                        + serviceName + ", methodName = " + methodName);
                throw new Error("init cmd.xml'serviceMethod definition, but can't find the service: serviceName = "
                        + serviceName + ", methodName = " + methodName);
            }

            Method[] methods = service.getClass().getMethods();

            boolean hadFind = false;
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    FastClass fastClass = FastClass.create(service.getClass());
                    FastMethod fastMethod = fastClass.getMethod(methodName, method.getParameterTypes());
                    MethodInvoker methodInvoker = new MethodInvoker(fastMethod, service, cmdProperties.getIndex());
                    methodInvokers[i] = methodInvoker;
                    hadFind = true;
                }
            }
            if (!hadFind) {
                logger.error("init cmd.xml'serviceMethod definition, but can't find the method: serviceName = "
                        + serviceName + ", methodName = " + methodName);
                throw new Error("init cmd.xml'serviceMethod definition, but can't find the method: serviceName = "
                        + serviceName + ", methodName = " + methodName);
            }
        }
    }

    /**
     * 
     * @param context
     * @param cmd
     */
    public void dispatch(final SynRoomPlayer player, final SynRoomBusinessCommand cmd) throws Exception {
        final ReqCmdProperties cmdProperties = cmd.getCmdProperties();

        if (cmdProperties.isGlobalCmd()) {
            globalThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        dispatch0(cmd, cmdProperties, player);
                    } catch (Throwable e) {
                        exceptionHandler.handleException(e, player.getChannelHandlerCtx());
                    }
                }
            });

        } else if (cmdProperties.isSynRoomCmd()) {
            SynRoom synRoom = player.getLocation();
            if (synRoom == null) {
                logger.error("get an synRoom msg,but the location is null, playerId=" + player.getPlayerId());
                return;
            }
            businessThreadPool.execute(new SynRoomTask(synRoom, player.getChannelHandlerCtx()) {
                @Override
                public void processTask() {
                    try {
                        dispatch0(cmd, cmdProperties, player);
                    } catch (Throwable e) {
                        exceptionHandler.handleException(e, player.getChannelHandlerCtx());
                    }
                }
            });
        } else if (cmdProperties.isDirectCmd()) {
            try {
                dispatch0(cmd, cmdProperties, player);
            } catch (Throwable e) {
                exceptionHandler.handleException(e, player.getChannelHandlerCtx());
            }
        }
    }

    private void dispatch0(SynRoomBusinessCommand cmd, ReqCmdProperties cmdProperties, SynRoomPlayer player)
            throws Throwable {
        GeneratedMessage proto = cmd.getProto();
        String serviceName = cmdProperties.getServiceName();
        String methodName = cmdProperties.getMethodName();
        List<FieldDescriptor> fields = proto.getDescriptorForType().getFields();
        Object[] params = getSortedParams(fields, proto, player); //把player放入params作为最后一个参数
        MethodInvoker methodInvoker = methodInvokers[cmdProperties.getIndex()]; //TODO

        if (methodInvoker == null) {
            logger.error("Can't find the method: serviceName = " + serviceName + ", methodName = " + methodName
                    + ", protoName = " + proto.getClass().getSimpleName() + ", paraNum = " + fields.size());
        }

        try {
            methodInvoker.invoke(params);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
    }


    private Object[] getSortedParams(List<FieldDescriptor> fields, GeneratedMessage proto, SynRoomPlayer player) {
        int size = fields.size();
        Object[] params = new Object[size + 1];
        for (int i = 0; i < size; i++) {
            FieldDescriptor fieldDescriptor = fields.get(i);
            params[i] = proto.getField(fieldDescriptor);
        }
        params[size] = player;
        return params;
    }

}
